package com.icongyou.enterprise.talent_analysis.service.impl;

import com.icongyou.enterprise.common.exception.BusinessException;
import com.icongyou.enterprise.talent_analysis.entity.DimensionDetail;
import com.icongyou.enterprise.talent_analysis.entity.MetricDetail;
import com.icongyou.enterprise.talent_analysis.entity.PortraitResponse;
import com.icongyou.enterprise.talent_analysis.entity.PortraitTemplate;
import com.icongyou.enterprise.talent_analysis.entity.dto.StudentFeatureContext;
import com.icongyou.enterprise.talent_analysis.entity.template.TemplateDimension;
import com.icongyou.enterprise.talent_analysis.entity.template.TemplateMetricMapping;
import com.icongyou.enterprise.talent_analysis.mapper.PortraitTemplateMapper;
import com.icongyou.enterprise.talent_analysis.service.AIPortraitEvaluationService;
import com.icongyou.enterprise.talent_analysis.service.support.AIClient;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * AI辅助的人才画像评估服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AIPortraitEvaluationServiceImpl implements AIPortraitEvaluationService {

    private final StudentFeatureExtractor featureExtractor;
    private final PortraitTemplateMapper templateMapper;
    private final AIClient aiClient;
    private final ObjectMapper objectMapper;
    private final com.icongyou.enterprise.talent_analysis.service.PortraitService portraitService;

    @Override
    public PortraitResponse computeCustomPortrait(String studentId, String templateId) {
        // 1. 加载模板
        PortraitTemplate template = templateMapper.selectById(templateId);
        if (template == null) {
            throw new BusinessException("模板不存在");
        }
        
        List<TemplateDimension> dimensions = parseDimensions(template.getDimensionsJson());
        
        // 1.5 检查维度名称是否有实际含义
        boolean dimensionsUnclear = areDimensionsUnclear(dimensions);
        
        if (dimensionsUnclear) {
            
            // 降级:使用默认维度计算
            PortraitResponse defaultResponse = portraitService.computeDefaultPortrait(studentId);
            defaultResponse.setDimensionsUnclear(true);
            defaultResponse.setFallbackToDefault(true);
            defaultResponse.setDimensionWarningMessage(
                "检测到自定义维度定义不明确(缺少实际含义),已自动使用默认的6个标准维度(技术能力、问题解决、学习能力、协作能力、沟通能力、执行能力)进行评估。建议重新定义具有明确含义的维度名称。"
            );
            return defaultResponse;
        }
        
        // 调试: 记录模板读取后的metrics顺序
        for (TemplateDimension dim : dimensions) {
            if (dim.getMetrics() != null && !dim.getMetrics().isEmpty()) {
            }
        }
        
        for (int i = 0; i < dimensions.size(); i++) {
            TemplateDimension dim = dimensions.get(i);
            int metricCount = (dim.getMetrics() == null) ? -1 : dim.getMetrics().size();
        }
        
        // 2. 提取学生特征
        StudentFeatureContext features = extractStudentFeatures(studentId);
        
        // 3. 检查是否需要AI生成评分标准
        boolean needsAIScoring = dimensions.stream()
            .anyMatch(dim -> dim.getMetrics() == null || dim.getMetrics().isEmpty());
        
        
        if (needsAIScoring && !template.getAiSuggested()) {
            // 首次使用AI生成评分标准并缓存
            List<StudentFeatureContext> samples = Collections.singletonList(features);
            dimensions = generateScoringStandards(dimensions, samples);
            
            // 更新模板:标记AI已生成并保存标准
            template.setAiSuggested(true);
            String dimensionsJsonToSave = serializeDimensions(dimensions);
            template.setDimensionsJson(dimensionsJsonToSave);
            
            // 调试: 记录保存前的内容编码状态
            for (TemplateDimension dim : dimensions) {
                if (dim.getTextAnalysisRules() != null) {
                }
            }
            
            try {
                templateMapper.updateById(template);
            } catch (Exception e) {
                // 继续执行,使用内存中的标准进行评分
            }
        }
        
        // 4. 执行评分
        PortraitResponse response = new PortraitResponse();
        response.setAiUsed(true);
        
        for (TemplateDimension dim : dimensions) {
            DimensionDetail detail = evaluateDimension(dim, features);
            response.getDimensions().add(detail);
            response.getLabels().add(dim.getName());
            response.getScores().add(detail.getScore());
        }
        
        return response;
    }

    @Override
    public StudentFeatureContext extractStudentFeatures(String studentId) {
        return featureExtractor.extractFeatures(studentId);
    }

    @Override
    public List<TemplateDimension> generateScoringStandards(
            List<TemplateDimension> dimensions, 
            List<StudentFeatureContext> sampleFeatures) {
        
        
        for (TemplateDimension dim : dimensions) {
            if (dim.getMetrics() != null && !dim.getMetrics().isEmpty()) {
                continue; // 已有指标,跳过
            }
            
            
            // 构建AI提示词
            String prompt = buildScoringStandardPrompt(dim, sampleFeatures);
            
            // 调用AI生成评分标准
            String aiResponse = aiClient.generateScoringStandard(prompt);
            
            // 解析AI响应并填充指标
            List<TemplateMetricMapping> metrics = parseAIScoringResponse(aiResponse, dim);
            dim.setMetrics(metrics);
            
        }
        
        return dimensions;
    }

    @Override
    public List<String> recommendDataMapping(String dimensionName, String dimensionDescription) {
        String normalizedDescription = normalizeDescription(dimensionName, dimensionDescription);
        String prompt = String.format(
            "维度名称: %s\n维度补充说明: %s\n\n" +
            "请从以下数据库字段中推荐最相关的5-8个字段用于评估该维度:\n" +
            "可用字段: avg_course_score, avg_assignment_score, assignment_pass_rate, " +
            "submission_like_index, course_completion_rate, learning_speed_index, " +
            "rework_rate_inverse, contribution_rate_avg, avg_team_score, " +
            "teacher_comment_rate, discussion_count, on_time_submission_rate, deadline_miss_inverse\n\n" +
            "同时考虑以下文本数据:\n" +
            "- grades.text_comment (教师评语)\n" +
            "- assignments.description (任务要求)\n" +
            "- grades.ai_text_comment (AI评语)\n" +
            "- submissions.reflection (团队协作/贡献描述)\n\n" +
            "请以JSON数组格式返回推荐字段列表",
            dimensionName, normalizedDescription
        );
        
        String response = aiClient.recommendMapping(prompt);
        // 解析返回的字段列表
        return parseRecommendedFields(response);
    }

    /**
     * 评估单个维度得分
     */
    private DimensionDetail evaluateDimension(TemplateDimension dim, StudentFeatureContext features) {
        DimensionDetail detail = new DimensionDetail();
        detail.setName(dim.getName());
        
        if (dim.getMetrics() == null || dim.getMetrics().isEmpty()) {
            // 使用AI进行整体评估
            double aiScore = evaluateWithAI(dim, features);
            detail.setScore(aiScore);
            detail.setInsufficientData(false);
            return detail;
        }
        
        // 1. 计算数值指标得分
        double numericScore = 0.0;
        double weightedSum = 0.0;
        double totalWeight = 0.0;
        
        for (TemplateMetricMapping metric : dim.getMetrics()) {
            Double value = extractMetricValue(metric, features);
            if (value != null) {
                // 归一化指标值,传入features以获取团队人数等上下文信息
                Double normalizedValue = normalizeMetric(metric.getMetricKey(), value, features);
                if (normalizedValue != null) {
                    weightedSum += normalizedValue * metric.getWeight();
                    totalWeight += metric.getWeight();
                    
                    MetricDetail md = new MetricDetail();
                    md.setMetric(metric.getMetricKey());
                    md.setRaw(value);
                    md.setNorm(normalizedValue); // 使用归一化后的值
                    md.setWeight(metric.getWeight());
                    detail.getDetails().add(md);
                }
            }
        }
        
        if (totalWeight > 0) {
            numericScore = (weightedSum / totalWeight) / 20.0; // 转换到0-5
        }
        
        // 2. 如果有文本分析规则,结合文本数据调整分数
        if (dim.getTextAnalysisRules() != null && !dim.getTextAnalysisRules().isEmpty()) {
            double textAdjustment = analyzeTextData(dim, features);
            numericScore = Math.max(0, Math.min(5.0, numericScore + textAdjustment));
        }
        
        if (totalWeight > 0 || (dim.getTextAnalysisRules() != null && !dim.getTextAnalysisRules().isEmpty())) {
            double finalScore = roundToOneDecimal(numericScore);
            detail.setScore(finalScore);
            detail.setInsufficientData(false);
        } else {
            detail.setScore(0.0);
            detail.setInsufficientData(true);
        }
        
        return detail;
    }

    /**
     * 分析文本数据并返回调整分数(-1到+1)
     * 优先使用本地关键词匹配(快速、免费)，AI作为可选辅助
     */
    private double analyzeTextData(TemplateDimension dim, StudentFeatureContext features) {
        try {
            // 收集所有文本数据
            StringBuilder textData = new StringBuilder();
            int textCount = 0;
            
            // 1. 教师评语 - 最重要的文本数据
            if (features.getTeacherComments() != null && !features.getTeacherComments().isEmpty()) {
                textData.append("【教师评语】\n");
                for (StudentFeatureContext.TeacherComment comment : features.getTeacherComments()) {
                    textData.append("任务: ").append(comment.getAssignmentName()).append("\n");
                    textData.append("评语: ").append(comment.getCommentText()).append("\n");
                    if (comment.getIsRecommended() != null && comment.getIsRecommended()) {
                        textData.append("(教师推荐作品)\n");
                    }
                    textData.append("\n");
                    textCount++;
                }
            }
            
            // 2. 任务详情 - 从assignments中提取教师评语
            if (features.getAssignments() != null && !features.getAssignments().isEmpty()) {
                boolean hasTaskComments = false;
                for (StudentFeatureContext.AssignmentFeature assignment : features.getAssignments()) {
                    if (assignment.getTeacherTextComment() != null && !assignment.getTeacherTextComment().isEmpty()) {
                        if (!hasTaskComments) {
                            textData.append("【任务教师评价】\n");
                            hasTaskComments = true;
                        }
                        textData.append("任务: ").append(assignment.getAssignmentName());
                        textData.append(" (类型: ").append(assignment.getAssignmentType()).append(")\n");
                        textData.append("评价: ").append(assignment.getTeacherTextComment()).append("\n");
                        if (assignment.getIsTeacherRecommended() != null && assignment.getIsTeacherRecommended()) {
                            textData.append("(推荐作品)\n");
                        }
                        textData.append("\n");
                        textCount++;
                    }
                }
            }
            
            // 3. 任务类型分布 - 辅助信息
            if (features.getAssignments() != null && !features.getAssignments().isEmpty()) {
                textData.append("【完成的任务类型】\n");
                long individualTasks = features.getAssignments().stream()
                    .filter(a -> "个人".equals(a.getAssignmentType()) || "0".equals(a.getAssignmentType()))
                    .count();
                long teamTasks = features.getAssignments().stream()
                    .filter(a -> "团队".equals(a.getAssignmentType()) || "1".equals(a.getAssignmentType()))
                    .count();
                textData.append("个人任务: ").append(individualTasks).append("个\n");
                textData.append("团队任务: ").append(teamTasks).append("个\n");
            }
            
            if (textCount == 0) {
                return 0.0; // 没有有效的文本数据
            }
            
            String textContent = textData.toString();
            
            // 调试: 记录文本规则和评语内容
            
            // 优先尝试本地关键词匹配（快速、免费）
            Double localScore = analyzeTextLocally(dim, textContent, textCount);
            if (localScore != null) {
                return localScore;
            }
            
            // 如果本地分析失败，回退到AI分析（同时进行增量学习）
            
            // 构建AI提示词
            String prompt = String.format(
                "请基于以下学生的教师评语,对【%s】维度进行文本分析打分:\n\n" +
                "维度分析规则:\n%s\n\n" +
                "学生教师评语:\n%s\n\n" +
                "评分标准:\n" +
                "1. 统计评语中出现的关键词数量(每个相关关键词+0.1分)\n" +
                "2. 识别正面评价(如'优秀', '出色', '表现好'等, 每条+0.3分)\n" +
                "3. 识别负面评价(如'需改进', '不够', '较差'等, 每条-0.3分)\n" +
                "4. 识别特殊加分项(如'教师推荐', '创新', '突出'等, 每项+0.5分)\n\n" +
                "请返回一个-1到+1之间的分数作为该维度的文本调整分:\n" +
                "- 如果评语显示该维度表现很好: 返回正数(0.3到1.0)\n" +
                "- 如果评语显示该维度表现一般: 返回0或接近0的数\n" +
                "- 如果评语显示该维度需要改进: 返回负数(-0.3到-1.0)\n" +
                "- 如果评语中完全没有提及该维度: 返回0\n\n" +
                "重要: 只返回一个数字,不要返回任何解释文字。",
                dim.getName(),
                dim.getTextAnalysisRules() != null ? dim.getTextAnalysisRules() : "通用评价",
                textData.toString()
            );
            
            String response = aiClient.evaluateDimension(prompt);
            double adjustment = Double.parseDouble(response.trim());
            double bounded = Math.max(-1.0, Math.min(1.0, adjustment));
            
            
            return bounded;
            
        } catch (Exception e) {
            return 0.0; // 失败时不调整
        }
    }

    /**
     * 本地关键词匹配分析（无需AI调用）
     * @return 调整分数，如果解析失败返回null
     */
    private Double analyzeTextLocally(TemplateDimension dim, String textContent, int textCount) {
        try {
            String rules = dim.getTextAnalysisRules();
            if (rules == null || rules.trim().isEmpty()) {
                return null;
            }
            
            // 尝试解析为JSON结构
            Map<String, Object> rulesMap = objectMapper.readValue(rules, new TypeReference<Map<String, Object>>() {});
            
            // 提取关键词列表（支持多种字段名）
            List<String> keywords = extractStringList(rulesMap, "keywords", "关键词列表", "关键词");
            List<String> positiveIndicators = extractStringList(rulesMap, "positiveIndicators", "正面指标", "正面指标");
            List<String> negativeIndicators = extractStringList(rulesMap, "negativeIndicators", "负面指标", "负面指标");
            List<String> bonusItems = extractStringList(rulesMap, "bonusPoints", "bonusItems", "特殊加分项", "特殊加分项");
            
            if (keywords.isEmpty() && positiveIndicators.isEmpty()) {
                return null;
            }
            
            // 统计命中
            int keywordHits = countMatches(textContent, keywords);
            int positiveHits = countMatches(textContent, positiveIndicators);
            int negativeHits = countMatches(textContent, negativeIndicators);
            int bonusHits = countMatches(textContent, bonusItems);
            
            
            // 计算调整分数（与AI提示词中的规则一致）
            double adjustment = 0.0;
            adjustment += keywordHits * 0.05;     // 每个关键词命中 +0.05
            adjustment += positiveHits * 0.2;     // 每个正面指标 +0.2
            adjustment -= negativeHits * 0.2;     // 每个负面指标 -0.2
            adjustment += bonusHits * 0.4;        // 每个加分项 +0.4
            
            // 限制在 -1.0 到 +1.0 范围
            return Math.max(-1.0, Math.min(1.0, adjustment));
            
        } catch (Exception e) {
            return null; // 解析失败，回退到AI
        }
    }
    
    /**
     * 🔄 增量学习：从AI响应中提取新关键词并更新到数据库
     * 当AI分析文本时，会识别出评语中的关键短语，将其添加到本地关键词库
     */
    private void learnFromAIResponse(TemplateDimension dim, String textContent, String aiResponse) {
        try {
            // 构建学习提示：让AI提取评语中与该维度相关的关键词
            String learningPrompt = String.format(
                "分析以下教师评语，提取与【%s】维度相关的关键词/短语（3-8个），以JSON数组格式返回。\n" +
                "只返回JSON，不要其他说明。\n" +
                "示例格式: {\"keywords\": [\"主动学习\", \"进步明显\"], \"positive\": [\"表现优秀\"], \"negative\": []}\n\n" +
                "评语内容：%s",
                dim.getName(),
                textContent.length() > 200 ? textContent.substring(0, 200) + "..." : textContent
            );
            
            // 使用analyzeText方法获取关键词（复用现有AI接口）
            String learningResponse = aiClient.analyzeText(learningPrompt, "keywords");
            
            // 解析AI返回的新关键词
            Map<String, Object> learnedData = parseLearnedKeywords(learningResponse);
            if (learnedData == null || learnedData.isEmpty()) {
                return;
            }
            
            // 合并到现有的textAnalysisRules
            boolean updated = mergeLearnedKeywords(dim, learnedData);
            
            if (updated) {
                // 标记需要更新：更新内存中的模板数据（由上层调用者负责持久化）
                // 可选：直接保存到数据库
                try {
                    PortraitTemplate template = templateMapper.selectById(dim.getCode().split("-")[0]); // 从code提取templateId
                    if (template != null) {
                        templateMapper.updateById(template);
                    } else {
                    }
                } catch (Exception e) {
                }
            }
            
        } catch (Exception e) {
            // 学习失败不影响主流程，只记录日志
        }
    }
    
    /**
     * 解析AI返回的学习结果
     */
    private Map<String, Object> parseLearnedKeywords(String aiResponse) {
        try {
            // 提取JSON部分
            String jsonPart = aiResponse;
            if (aiResponse.contains("{")) {
                int start = aiResponse.indexOf("{");
                int end = aiResponse.lastIndexOf("}") + 1;
                if (end > start) {
                    jsonPart = aiResponse.substring(start, end);
                }
            }
            
            return objectMapper.readValue(jsonPart, new TypeReference<Map<String, Object>>() {});
        } catch (Exception e) {
            return null;
        }
    }
    
    /**
     * 合并学到的新关键词到现有规则
     * @return 是否有更新
     */
    private boolean mergeLearnedKeywords(TemplateDimension dim, Map<String, Object> learnedData) {
        try {
            String existingRules = dim.getTextAnalysisRules();
            Map<String, Object> rulesMap;
            
            if (existingRules == null || existingRules.trim().isEmpty()) {
                // 如果没有现有规则，直接使用学到的
                rulesMap = new HashMap<>();
            } else {
                // 解析现有规则
                rulesMap = objectMapper.readValue(existingRules, new TypeReference<Map<String, Object>>() {});
            }
            
            boolean hasUpdate = false;
            
            // 合并各类关键词（去重）
            hasUpdate |= mergeKeywordList(rulesMap, learnedData, "keywords", "关键词列表", "关键词");
            hasUpdate |= mergeKeywordList(rulesMap, learnedData, "positiveIndicators", "positive", "正面指标");
            hasUpdate |= mergeKeywordList(rulesMap, learnedData, "negativeIndicators", "negative", "负面指标");
            hasUpdate |= mergeKeywordList(rulesMap, learnedData, "bonusPoints", "bonus", "加分项");
            
            if (hasUpdate) {
                // 更新维度对象的textAnalysisRules
                dim.setTextAnalysisRules(objectMapper.writeValueAsString(rulesMap));
            }
            
            return hasUpdate;
            
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * 合并单个关键词列表
     */
    private boolean mergeKeywordList(Map<String, Object> target, Map<String, Object> source, String... possibleKeys) {
        // 从target获取现有列表
        List<String> existingList = extractStringList(target, possibleKeys);
        Set<String> existingSet = new HashSet<>(existingList);
        int originalSize = existingSet.size();
        
        // 从source获取新学到的
        List<String> learnedList = extractStringList(source, possibleKeys);
        existingSet.addAll(learnedList);
        
        // 如果有新增，更新到target
        if (existingSet.size() > originalSize) {
            // 使用第一个key作为标准字段名
            target.put(possibleKeys[0], new ArrayList<>(existingSet));
            return true;
        }
        
        return false;
    }
    
    /**
     * 从Map中提取字符串列表（支持多个可能的key）
     */
    private List<String> extractStringList(Map<String, Object> map, String... possibleKeys) {
        for (String key : possibleKeys) {
            // 尝试原始key
            Object value = map.get(key);
            
            // 如果没找到，尝试带"? "前缀的key（AI生成的JSON可能带问号）
            if (value == null) {
                value = map.get("? " + key);
            }
            
            if (value instanceof List) {
                List<String> result = new ArrayList<>();
                for (Object item : (List<?>) value) {
                    if (item != null) {
                        result.add(item.toString());
                    }
                }
                return result;
            } else if (value instanceof String) {
                // 支持逗号分隔的字符串
                String str = (String) value;
                if (str.contains(",") || str.contains("/")) {
                    return Arrays.stream(str.split("[,/]+"))
                        .map(String::trim)
                        .filter(s -> !s.isEmpty())
                        .collect(Collectors.toList());
                }
            }
        }
        return new ArrayList<>();
    }
    
    /**
     * 统计文本中关键词出现次数
     */
    private int countMatches(String text, List<String> keywords) {
        if (text == null || keywords == null) return 0;
        int count = 0;
        String lowerText = text.toLowerCase();
        for (String keyword : keywords) {
            if (keyword != null && !keyword.trim().isEmpty()) {
                String lowerKeyword = keyword.toLowerCase().trim();
                int index = 0;
                while ((index = lowerText.indexOf(lowerKeyword, index)) != -1) {
                    count++;
                    index += lowerKeyword.length();
                }
            }
        }
        return count;
    }
    
    /**
     * 使用AI进行整体维度评估
     */
    private double evaluateWithAI(TemplateDimension dim, StudentFeatureContext features) {
        String normalizedDescription = normalizeDescription(dim.getName(), dim.getDescription());
        String prompt = String.format(
            "请基于以下学生数据,对【%s】维度进行评分(0-5分):\n\n" +
            "维度补充说明: %s\n\n" +
            "学生数据:\n%s\n\n" +
            "请返回一个0-5之间的浮点数评分,只需返回数字即可。",
            dim.getName(),
            normalizedDescription,
            features.toAIReadableText()
        );
        
        String response = aiClient.evaluateDimension(prompt);
        try {
            return Double.parseDouble(response.trim());
        } catch (NumberFormatException e) {
            return 3.0; // 默认中等分数
        }
    }

    /**
     * 构建生成评分标准的提示词
     */
    private String buildScoringStandardPrompt(TemplateDimension dim, List<StudentFeatureContext> samples) {
        StringBuilder prompt = new StringBuilder();
        prompt.append("你是一个人才评估专家。请为以下维度设计量化评分标准:\n\n");
        prompt.append(String.format("维度名称: %s\n", dim.getName()));
        prompt.append(String.format("维度补充说明: %s\n\n", normalizeDescription(dim.getName(), dim.getDescription())));
        
        prompt.append("⚠️ 重要要求: 必须根据维度特点选择最相关的3-5个指标,不同维度应该有不同的指标组合!\n\n");
        
        prompt.append("可用的数据源包括:\n");
        prompt.append("【数值指标详解】共14个可选指标:\n");
        prompt.append("1. avg_course_score - 课程平均分(0-100),反映整体学业成绩\n");
        prompt.append("2. avg_assignment_score - 作业平均分(0-100),反映日常表现\n");
        prompt.append("3. assignment_pass_rate - 作业通过率(0-1),反映基础掌握程度\n");
        prompt.append("4. submission_like_index - 作品点赞+收藏数均值,反映作品质量/创新性\n");
        prompt.append("5. course_completion_rate - 课程完成率(0-1),反映学习态度/自驱力\n");
        prompt.append("6. learning_speed_index - 学习速度指数(预期时长/实际时长),反映学习效率\n");
        prompt.append("7. rework_rate_inverse - 一次通过率(1-返工率),反映代码质量/理解深度\n");
        prompt.append("8. contribution_rate_avg - 团队贡献度均值(0-100),反映团队协作能力\n");
        prompt.append("9. avg_team_score - 团队项目平均分(0-100),反映协作成果\n");
        prompt.append("10. teacher_comment_rate - 教师评语率(0-1),反映师生互动/受关注度\n");
        prompt.append("11. discussion_count - 讨论次数,反映主动性/求知欲\n");
        prompt.append("12. on_time_submission_rate - 按时提交率(0-1),反映时间管理/责任心\n");
        prompt.append("13. deadline_miss_inverse - 逾期控制指数(1-逾期程度),反映计划性\n");
        prompt.append("14. avg_course_total_score - 课程总分均值(0-100),反映综合实力\n\n");
        
        prompt.append("【指标选择指南】根据维度特点选择最相关的指标:\n");
        prompt.append("- 技术能力/专业能力: avg_assignment_score(40%), rework_rate_inverse(30%), submission_like_index(20%), learning_speed_index(10%)\n");
        prompt.append("- 学习态度/自驱力: course_completion_rate(40%), on_time_submission_rate(30%), discussion_count(20%), learning_speed_index(10%)\n");
        prompt.append("- 团队协作: contribution_rate_avg(40%), avg_team_score(30%), teacher_comment_rate(20%), on_time_submission_rate(10%)\n");
        prompt.append("- 创新能力: submission_like_index(40%), avg_assignment_score(30%), rework_rate_inverse(20%), discussion_count(10%)\n");
        prompt.append("- 问题解决: assignment_pass_rate(40%), learning_speed_index(30%), rework_rate_inverse(20%), discussion_count(10%)\n");
        prompt.append("- 时间管理: on_time_submission_rate(50%), deadline_miss_inverse(30%), learning_speed_index(20%)\n");
        prompt.append("- 沟通能力: teacher_comment_rate(40%), discussion_count(30%), contribution_rate_avg(20%), avg_team_score(10%)\n");
        prompt.append("以上仅供参考,请根据实际维度名称和描述灵活选择!\n\n");
        
        prompt.append("【文本数据】教师评语(text_comment)、教师推荐标记、任务类型(个人/团队)、" +
            "任务描述、评分标准、是否按时提交等\n");
        prompt.append("注意: 只使用教师的真实评价,不要使用AI生成的评语\n\n");
        
        prompt.append("参考学生样本数据:\n");
        for (int i = 0; i < Math.min(2, samples.size()); i++) {
            prompt.append("样本" + (i+1) + ":\n");
            prompt.append(samples.get(i).toAIReadableText());
            prompt.append("\n---\n");
        }
        
        prompt.append("\n请返回JSON格式的评分标准,包含:\n");
        prompt.append("1. metrics: 选择3-5个最相关的数值指标,每个包含 metricKey(指标名) 和 weight(权重)\n");
        prompt.append("   ⚠️ 重要: \n");
        prompt.append("   - 所有metrics的weight之和必须严格等于1.0 (例如: 0.4+0.3+0.2+0.1=1.0)\n");
        prompt.append("   - 必须根据维度特点选择差异化指标,避免所有维度都用相同的指标组合\n");
        prompt.append("   - 优先选择最能体现该维度特征的指标\n\n");
        
        prompt.append("2. textAnalysisRules: 详细的文本分析规则,必须包含:\n");
        prompt.append("   ⚠️ 重要: 不同维度的textAnalysisRules必须有明显差异,关键词要高度针对该维度!\n");
        prompt.append("   a) 🔍 关键词列表: 列出25-35个与该维度强相关的关键词/短语(包含同义词和常见表达)\n");
        prompt.append("      - 技术能力: 'Java基础', 'OOP', 'Spring框架', '数据库', 'SQL', '算法', '数据结构', '设计模式', '代码质量', '代码规范', '性能优化', '架构设计', '系统设计', '分布式', '微服务', '高并发', '可扩展性', '模块化', '解耦', '技术选型', 'bug修复', '单元测试', '重构', '文档编写', '技术深度', '专业能力', '编程能力', '开发能力', '技术扎实', '基础牢固'\n");
        prompt.append("      - 学习态度: '主动学习', '自主学习', '求知欲', '好学', '自驱力', '学习能力', '坚持', '努力', '勤奋', '认真', '进步明显', '成长快', '不懂就问', '积极提问', '善于总结', '反思能力', '学习热情', '钻研精神', '持续学习', '快速上手', '理解能力', '吸收能力', '学习投入', '课外学习', '拓展学习', '深度学习'\n");
        prompt.append("      - 团队协作: '团队配合', '团队合作', '协作', '沟通', '交流', '帮助队友', '互助', '分工合理', '分工明确', '责任心', '可靠', '靠谱', '配合默契', '团队意识', '集体荣誉', '乐于分享', '知识共享', '相互支持', '协调能力', '组织能力', '领导力', '带队能力', '凝聚力', '团队氛围', '合作精神'\n");
        prompt.append("      - 创新能力: '创新', '创意', '独特', '新颖', '原创', '突破', '改进', '优化', '创造性', '想象力', '创新思维', '创新意识', '敢于尝试', '勇于创新', '打破常规', '思路独特', '方案创新', '技术创新', '设计创新', '功能创新', '别出心裁', '与众不同', '有想法', '有创意', '创新点'\n");
        prompt.append("      - 问题解决: '分析问题', '解决问题', '问题意识', '解决方案', '调试', 'debug', '排查', '定位问题', '思路清晰', '逻辑严谨', '逻辑性强', '分析能力', '解决能力', '应变能力', '独立解决', '高效解决', '快速定位', '根因分析', '举一反三', '触类旁通', '灵活应对', '处理问题'\n");
        prompt.append("   b) ➕ 正面指标: 列出15-20个正面评价短语(代表该维度表现优秀)\n");
        prompt.append("      - 例如: '设计合理', '架构清晰', '思路清晰', '主动学习', '配合默契', '创意十足', '基础扎实', '能力突出', '表现优异', '进步明显', '值得表扬', '非常出色', '令人满意', '超出预期', '完成出色' 等\n");
        prompt.append("   c) ➖ 负面指标: 列出10-15个负面评价短语(代表需要改进)\n");
        prompt.append("      - 例如: '理解不够深入', '代码不够规范', '沟通较少', '缺乏创新', '解决问题能力弱', '基础薄弱', '需要加强', '有待提高', '不够理想', '存在问题', '需要改进', '注意规范' 等\n");
        prompt.append("   d) ⭐ 特殊加分项: 5-10个突出表现(教师推荐、创新性成果、突出贡献等)\n");
        prompt.append("      - 例如: '教师推荐', '强烈推荐', '特别优秀', '独立完成复杂项目', '帮助多位同学', '主导团队', '获奖作品', '发表论文', '技术分享', '开源贡献' 等\n\n");
        
        prompt.append("3. scoringLogic: 如何将数值指标和文本分析组合成最终分数的具体逻辑\n");
        prompt.append("   ⚠️ 文本分析评分规则: 关键词命中+0.05分/个, 正面指标+0.2分/个, 负面指标-0.2分/个, 加分项+0.4分/个, 调整范围[-1.0, +1.0]\n\n");
        
        prompt.append("JSON格式示例(技术能力维度):\n");
        prompt.append("{\n");
        prompt.append("  \"metrics\": [\n");
        prompt.append("    {\"metricKey\": \"avg_assignment_score\", \"weight\": 0.4},\n");
        prompt.append("    {\"metricKey\": \"rework_rate_inverse\", \"weight\": 0.3},\n");
        prompt.append("    {\"metricKey\": \"submission_like_index\", \"weight\": 0.2},\n");
        prompt.append("    {\"metricKey\": \"learning_speed_index\", \"weight\": 0.1}\n");
        prompt.append("  ],\n");
        prompt.append("  \"textAnalysisRules\": \"关键词: ['架构设计', '系统设计', '分布式', '微服务', '高并发', '可扩展性', '模块化', '解耦', '性能优化', '技术选型', '代码质量', '代码规范', 'bug修复', '算法实现', '数据结构', 'Java基础', 'OOP', 'Spring框架', '数据库设计', 'SQL优化', '设计模式', '单元测试', '重构能力', '技术深度', '专业能力', '编程能力', '开发能力', '技术扎实', '基础牢固', '掌握全面']; 正面: ['设计合理', '架构清晰', '考虑全面', '代码优雅', '性能优秀', '基础扎实', '能力突出', '技术全面', '实现出色', '质量很高', '非常专业', '掌握熟练', '运用得当', '理解深刻', '技术过硬']; 负面: ['设计欠缺', '架构混乱', '缺乏整体考虑', '代码冗余', '性能问题', '基础薄弱', '理解不够', '需要加强', '不够规范', '存在缺陷']; 加分: ['教师推荐', '独立完成复杂架构', '技术分享', '帮助多位同学', '开源贡献', '获奖作品']\",\n");
        prompt.append("  \"scoringLogic\": \"基础分=数值指标加权平均(作业分40%+一次通过率30%+作品质量20%+学习速度10%); 文本调整=关键词命中数×0.05+正面评价数×0.2-负面评价数×0.2+加分项数×0.4(范围-1.0到+1.0); 最终分=基础分+文本调整(范围0-5分)\"\n");
        prompt.append("}\n\n");
        
        prompt.append("JSON格式示例(团队协作维度):\n");
        prompt.append("{\n");
        prompt.append("  \"metrics\": [\n");
        prompt.append("    {\"metricKey\": \"contribution_rate_avg\", \"weight\": 0.4},\n");
        prompt.append("    {\"metricKey\": \"avg_team_score\", \"weight\": 0.3},\n");
        prompt.append("    {\"metricKey\": \"teacher_comment_rate\", \"weight\": 0.2},\n");
        prompt.append("    {\"metricKey\": \"on_time_submission_rate\", \"weight\": 0.1}\n");
        prompt.append("  ],\n");
        prompt.append("  \"textAnalysisRules\": \"关键词: ['团队配合', '协作', '沟通', '分工', '责任心', '主动承担', '帮助队友', '冲突解决', '团队氛围', '领导力', '执行力', '可靠']; 正面: '积极配合/沟通顺畅/主动承担/团队核心'; 负面: '配合不足/沟通不畅/推卸责任/影响进度'; 加分: 担任团队负责人+获得队友高度认可\",\n");
        prompt.append("  \"scoringLogic\": \"基础分=数值指标加权平均(贡献度40%+团队分30%+互动20%+按时率10%); 文本调整=团队关键词×0.15+正面+0.4-负面-0.4+负责人+0.5; 最终分=基础分+文本调整(范围0-5分)\"\n");
        prompt.append("}\n\n");
        prompt.append("⚠️ 重要提示:\n");
        prompt.append("- 数值指标metrics必须根据维度差异化选择,避免雷同\n");
        prompt.append("- textAnalysisRules的关键词必须高度针对该维度,不同维度关键词应该明显不同\n");
        prompt.append("- 关键词要涵盖该维度的各个细分方面,数量要达到10-15个\n");
        prompt.append("- 权重总和必须等于1.0!\n\n");
        
        prompt.append("【常见维度的差异化示例】\n");
        prompt.append("技术能力 → metrics: avg_assignment_score, rework_rate_inverse, submission_like_index | 关键词: Java/Spring/数据库/算法\n");
        prompt.append("学习态度 → metrics: course_completion_rate, on_time_submission_rate, discussion_count | 关键词: 主动学习/求知欲/坚持/努力\n");
        prompt.append("团队协作 → metrics: contribution_rate_avg, avg_team_score, teacher_comment_rate | 关键词: 配合/沟通/协作/责任心\n");
        prompt.append("创新能力 → metrics: submission_like_index, discussion_count, learning_speed_index | 关键词: 创新/独特/新颖/改进\n");
        prompt.append("问题解决 → metrics: assignment_pass_rate, learning_speed_index, rework_rate_inverse | 关键词: 分析/解决方案/调试/逻辑\n");
        prompt.append("时间管理 → metrics: on_time_submission_rate, deadline_miss_inverse, learning_speed_index | 关键词: 计划/准时/高效/时间安排");
        
        return prompt.toString();
    }

    /**
     * 解析AI返回的评分标准
     */
    private List<TemplateMetricMapping> parseAIScoringResponse(String aiResponse, TemplateDimension dim) {
        try {
            // 优先提取 code fence 中的主 JSON, 避免抓取到示例或提示词中的其他花括号
            String json = extractPrimaryJsonBlock(aiResponse);
            if (json == null) {
                json = extractJsonFromResponse(aiResponse); // 回退到旧策略
            }
            
            // 移除JSON中的注释（AI有时会添加//注释）
            json = removeJsonComments(json);
            
            Map<String, Object> parsed = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
            if (parsed == null) {
                return new ArrayList<>();
            }
            // 记录原始 keys 方便调试指标错位
            Object metricsRaw = parsed.get("metrics");
            if (metricsRaw == null) {
            }
            if (!(metricsRaw instanceof List)) {
            }
            
            // 解析数值指标
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> metricsData = metricsRaw instanceof List ? (List<Map<String, Object>>) metricsRaw : Collections.emptyList();
            List<TemplateMetricMapping> metrics = new ArrayList<>();
            
            for (Map<String, Object> m : metricsData) {
                TemplateMetricMapping mapping = new TemplateMetricMapping();
                mapping.setMetricKey((String) m.get("metricKey"));
                mapping.setWeight(((Number) m.get("weight")).doubleValue());
                mapping.setNormalization("ai_generated");
                metrics.add(mapping);
            }
            
            // 验证并归一化权重总和
            double weightSum = metrics.stream()
                .mapToDouble(TemplateMetricMapping::getWeight)
                .sum();
            
            if (Math.abs(weightSum - 1.0) > 0.01) {
                // 自动归一化
                for (TemplateMetricMapping metric : metrics) {
                    double normalizedWeight = metric.getWeight() / weightSum;
                    metric.setWeight(Math.round(normalizedWeight * 100.0) / 100.0); // 保留2位小数
                }
                
                // 再次验证，如果还有微小误差，调整最大权重项
                double newSum = metrics.stream().mapToDouble(TemplateMetricMapping::getWeight).sum();
                if (Math.abs(newSum - 1.0) > 0.001) {
                    TemplateMetricMapping maxWeightMetric = metrics.stream()
                        .max((m1, m2) -> Double.compare(m1.getWeight(), m2.getWeight()))
                        .orElse(metrics.get(0));
                    maxWeightMetric.setWeight(maxWeightMetric.getWeight() + (1.0 - newSum));
                }
                
            }
            
            // 保存文本分析规则 (兼容字符串 / Map / List)
            if (parsed.containsKey("textAnalysisRules")) {
                Object textRulesObj = parsed.get("textAnalysisRules");
                try {
                    if (textRulesObj instanceof String) {
                        dim.setTextAnalysisRules((String) textRulesObj);
                    } else if (textRulesObj instanceof Map || textRulesObj instanceof List) {
                        // 序列化为紧凑JSON字符串存储
                        dim.setTextAnalysisRules(objectMapper.writeValueAsString(textRulesObj));
                    } else {
                        dim.setTextAnalysisRules(String.valueOf(textRulesObj));
                    }
                } catch (Exception serEx) {
                    dim.setTextAnalysisRules(String.valueOf(textRulesObj));
                }
            }

            // 保存评分逻辑说明 (若AI返回对象也做兼容)
            if (parsed.containsKey("scoringLogic")) {
                Object scoringLogicObj = parsed.get("scoringLogic");
                if (scoringLogicObj instanceof String) {
                    dim.setScoringLogic((String) scoringLogicObj);
                } else {
                    try {
                        dim.setScoringLogic(objectMapper.writeValueAsString(scoringLogicObj));
                    } catch (Exception ex) {
                        dim.setScoringLogic(String.valueOf(scoringLogicObj));
                    }
                }
            }
            
            
            return metrics;
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }

    /**
     * 提取指标值
     */
    private Double extractMetricValue(TemplateMetricMapping metric, StudentFeatureContext features) {
        Object value = features.getNumericMetrics().get(metric.getMetricKey());
        if (value == null) {
            return null;
        }
        return ((Number) value).doubleValue();
    }

    /**
     * 归一化指标值到0-100范围
     */
    private Double normalizeMetric(String metric, Double raw, StudentFeatureContext features) {
        if (raw == null) return null;
        switch (metric) {
            case "avg_course_score":
            case "avg_assignment_score":
            case "avg_team_score":
            case "avg_course_total_score":
                // 0~100 直接
                return clamp(raw, 0, 100);
            case "assignment_pass_rate":
            case "course_completion_rate":
            case "rework_rate_inverse":
            case "on_time_submission_rate":
            case "deadline_miss_inverse":
            case "teacher_comment_rate":
                // 比率:若>1 视作百分数已换算
                return raw > 1 ? clamp(raw, 0, 100) : clamp(raw * 100, 0, 100);
            case "contribution_rate_avg":
                // 贡献度: 动态获取团队平均人数,计算相对倍数
                // 获取团队平均人数(处理BigDecimal类型)
                Object avgTeamSizeObj = features.getNumericMetrics().get("avg_team_size");
                Double avgTeamSize = null;
                if (avgTeamSizeObj != null) {
                    avgTeamSize = ((Number) avgTeamSizeObj).doubleValue();
                }
                double baseContribution = (avgTeamSize != null && avgTeamSize > 0) ? (100.0 / avgTeamSize) : 20.0;
                
                // 归一化原始值到0-100范围
                double normalizedValue = raw <= 1 ? raw * 100 : raw;
                
                // 计算相对倍数
                double multiplier = normalizedValue / baseContribution;
                
                // 分段映射:
                // 0-1倍: 0-60分 (低于平均)
                // 1-1.1倍: 60-70分 (平均)
                // 1.1-1.5倍: 70-85分 (良好)
                // 1.5倍+: 85-100分 (优秀)
                double contributionScore;
                if (multiplier <= 1.0) {
                    contributionScore = multiplier * 60.0;
                } else if (multiplier <= 1.1) {
                    contributionScore = 60 + (multiplier - 1.0) * 100.0;
                } else if (multiplier <= 1.5) {
                    contributionScore = 70 + (multiplier - 1.1) * 37.5;
                } else {
                    contributionScore = 85 + Math.min((multiplier - 1.5) * 15.0, 15);
                }
                return clamp(contributionScore, 0, 100);
            case "learning_speed_index":
                // 期望/实际,设 cap=2.0(更快的上限)
                double cap = 2.0;
                return clamp((raw > cap ? cap : raw) / cap * 100, 0, 100);
            case "submission_like_index":
                // 点赞+收藏均值,设 cap=10
                double likeCap = 10.0;
                return clamp((raw > likeCap ? likeCap : raw) / likeCap * 100, 0, 100);
            case "discussion_count":
                // 讨论次数,设 cap=10
                double discCap = 10.0;
                return clamp((raw > discCap ? discCap : raw) / discCap * 100, 0, 100);
            default:
                return null;
        }
    }

    private static double clamp(double v, double min, double max) {
        return Math.max(min, Math.min(max, v));
    }

    private String extractJsonFromResponse(String response) {
        int start = response.indexOf("{");
        int end = response.lastIndexOf("}");
        if (start >= 0 && end > start) {
            return response.substring(start, end + 1);
        }
        return response;
    }

    /**
     * 更智能的主 JSON 提取：
     * 1. 优先匹配 ```json ... ``` 代码块（取包含 metrics 的最短块）
     * 2. 若存在多个代码块，选择含 "\"metrics\"" 且长度合理( < 50KB )的块
     * 3. 若无 code fence，则尝试使用正则匹配包含 "metrics" 的最外层花括号
     */
    private String extractPrimaryJsonBlock(String response) {
        if (response == null) return null;
        // 匹配 markdown json 代码块
        java.util.regex.Pattern fence = java.util.regex.Pattern.compile("```json\\s*(\n|\r\n)?(.*?)```", java.util.regex.Pattern.DOTALL);
        java.util.regex.Matcher m = fence.matcher(response);
        List<String> candidates = new ArrayList<>();
        while (m.find()) {
            String block = m.group(2).trim();
            if (block.contains("\"metrics\"")) {
                candidates.add(block);
            }
        }
        if (!candidates.isEmpty()) {
            // 选择最短的一个，避免嵌套示例拼接
            candidates.sort(Comparator.comparingInt(String::length));
            return candidates.get(0);
        }
        // 尝试正则抓取包含 metrics 的 JSON 对象（非贪婪）
        java.util.regex.Pattern obj = java.util.regex.Pattern.compile("\\{(?s).*?\"metrics\".*?\\}");
        java.util.regex.Matcher m2 = obj.matcher(response);
        if (m2.find()) {
            return m2.group();
        }
        return null; // 回退由旧方法处理
    }
    
    /**
     * 移除JSON字符串中的单行注释
     */
    private String removeJsonComments(String json) {
        // 移除 // 开头的单行注释
        return json.replaceAll("//[^\n\r]*", "");
    }

    private List<String> parseRecommendedFields(String response) {
        try {
            String json = extractJsonFromResponse(response);
            return objectMapper.readValue(json, new TypeReference<List<String>>() {});
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }

    private List<TemplateDimension> parseDimensions(String json) {
        try {
            // 尝试解析为Map结构 {"维度名称": {...}}
            if (json != null && json.trim().startsWith("{")) {
                com.fasterxml.jackson.core.type.TypeReference<java.util.Map<String, java.util.Map<String, Object>>> mapType = 
                    new com.fasterxml.jackson.core.type.TypeReference<java.util.Map<String, java.util.Map<String, Object>>>() {};
                java.util.Map<String, java.util.Map<String, Object>> dimensionMap = objectMapper.readValue(json, mapType);
                
                List<TemplateDimension> dimensions = new ArrayList<>();
                for (java.util.Map.Entry<String, java.util.Map<String, Object>> entry : dimensionMap.entrySet()) {
                    TemplateDimension dimension = new TemplateDimension();
                    dimension.setName(entry.getKey());
                    dimension.setCode(entry.getKey()); // 使用名称作为code
                    
                    java.util.Map<String, Object> dimData = entry.getValue();
                    if (dimData.containsKey("weight")) {
                        dimension.setWeight(((Number) dimData.get("weight")).doubleValue());
                    }
                    
                    // 解析indicators为metrics
                    if (dimData.containsKey("indicators")) {
                        @SuppressWarnings("unchecked")
                        List<java.util.Map<String, Object>> indicators = (List<java.util.Map<String, Object>>) dimData.get("indicators");
                        List<TemplateMetricMapping> metrics = new ArrayList<>();
                        for (java.util.Map<String, Object> indicator : indicators) {
                            TemplateMetricMapping metric = new TemplateMetricMapping();
                            metric.setMetricKey((String) indicator.get("name"));
                            if (indicator.containsKey("weight")) {
                                metric.setWeight(((Number) indicator.get("weight")).doubleValue());
                            }
                            metrics.add(metric);
                        }
                        dimension.setMetrics(metrics);
                    }
                    
                    dimensions.add(dimension);
                }
                return dimensions;
            }
            
            // 如果是数组格式,直接解析
            return objectMapper.readValue(json, new TypeReference<List<TemplateDimension>>() {});
        } catch (Exception e) {
            throw new BusinessException("解析模板维度失败: " + e.getMessage());
        }
    }

    private String serializeDimensions(List<TemplateDimension> dimensions) {
        try {
            return objectMapper.writeValueAsString(dimensions);
        } catch (Exception e) {
            throw new BusinessException("序列化维度失败");
        }
    }

    private double roundToOneDecimal(double value) {
        return Math.round(value * 10.0) / 10.0;
    }

    private String normalizeDescription(String dimensionName, String originalDescription) {
        if (originalDescription == null || originalDescription.isBlank()) {
            return String.format("HR仅提供了维度名称 \"%s\" , 请结合该名称、学生表现与历史数据自行理解其含义并生成评分标准。", dimensionName);
        }
        return originalDescription;
    }
    
    /**
     * 检查维度名称是否不明确
     * 判断标准:
     * 1. 维度名称过短(少于2个字符)
     * 2. 维度名称只包含无意义的单字符(如A、B、C、D、E、F)
     * 3. 维度名称只包含数字
     * 4. 维度名称是连续的字母序列(如ABC、ABCD等)
     * 
     * @param dimensions 维度列表
     * @return true表示维度不明确,false表示维度明确
     */
    private boolean areDimensionsUnclear(List<TemplateDimension> dimensions) {
        if (dimensions == null || dimensions.isEmpty()) {
            return true;
        }
        
        int unclearCount = 0;
        for (TemplateDimension dim : dimensions) {
            String name = dim.getName();
            
            if (name == null || name.isBlank()) {
                unclearCount++;
                continue;
            }
            
            name = name.trim();
            
            // 检查1: 名称过短
            if (name.length() < 2) {
                unclearCount++;
                continue;
            }
            
            // 检查2: 只包含单个字母(不论大小写)
            if (name.length() == 1 && Character.isLetter(name.charAt(0))) {
                unclearCount++;
                continue;
            }
            
            // 检查3: 只包含数字
            if (name.matches("^\\d+$")) {
                unclearCount++;
                continue;
            }
            
            // 检查4: 是否为连续字母序列(如ABC、ABCD、ABCDEF等)
            if (isConsecutiveLetterSequence(name)) {
                unclearCount++;
                continue;
            }
            
            // 检查5: 是否只包含重复的字符(如AAA、BBB)
            if (name.length() >= 2 && name.chars().distinct().count() == 1) {
                unclearCount++;
                continue;
            }
        }
        
        // 如果超过一半的维度不明确,则认为整体维度定义不明确
        boolean result = unclearCount > dimensions.size() / 2;
        return result;
    }
    
    /**
     * 判断字符串是否为连续字母序列
     * 例如: ABC、ABCD、ABCDEF、XYZ等
     */
    private boolean isConsecutiveLetterSequence(String str) {
        if (str == null || str.length() < 2) {
            return false;
        }
        
        String upper = str.toUpperCase();
        
        // 检查是否全为字母
        if (!upper.matches("^[A-Z]+$")) {
            return false;
        }
        
        // 检查是否为连续序列
        for (int i = 1; i < upper.length(); i++) {
            if (upper.charAt(i) != upper.charAt(i - 1) + 1) {
                return false;
            }
        }
        
        return true;
    }
    
    @Override
    public Map<String, Integer> extractTeacherCommentKeywords(String studentId) {
        
        // 1. 提取学生特征，获取教师评语
        StudentFeatureContext features = extractStudentFeatures(studentId);
        
        List<String> comments = features.getTeacherComments().stream()
            .map(StudentFeatureContext.TeacherComment::getCommentText)
            .filter(c -> c != null && !c.isBlank())
            .collect(Collectors.toList());
        
        if (comments.isEmpty()) {
            return new HashMap<>();
        }
        
        
        // 2. 调用AI提取关键词
        String keywordsJson = aiClient.extractKeywords(comments);
        
        // 3. 解析JSON结果
        try {
            String cleanJson = extractJsonFromResponse(keywordsJson);
            Map<String, Integer> keywords = objectMapper.readValue(
                cleanJson, 
                new TypeReference<Map<String, Integer>>() {}
            );
            
            return keywords;
        } catch (Exception e) {
            return new HashMap<>();
        }
    }
}